package projects;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;

public class DecisionTree extends SupervisedLearner {
	
	public void train(Matrix features, Matrix labels) throws Exception {		
		
		Matrix m_features = new Matrix(features, 0, 0, features.rows(), features.cols());
		Matrix m_labels = new Matrix(labels, 0, 0, labels.rows(), labels.cols());
				
		ArrayList<Double> attributes = new ArrayList<Double>();
		for (int i = 0; i < m_features.cols(); i++)
			attributes.add((double)i);
		
		double[][] featuresArray = DTTools.matrixTo2DArray(m_features);
		double[] labelsArray = DTTools.matrixToArray(m_labels);
	
		//--------------------------Validation Set Changes----------------------//
//		valFeatures = DTTools.saveValidationFeatures(m_features);
//		valLabels = DTTools.saveValidationFeatures(m_labels);
//		
//		featuresArray = DTTools.matrixTo2DArray(DTTools.nonValidationFeatures(m_features));
//		labelsArray = DTTools.matrixToArray(DTTools.nonValidationFeatures(m_labels));
		//--------------------------------------------------------------------//
		
//		DTTools.test(featuresArray, labelsArray, attributes);	
		
		// Experiment code for problem #6
//		featuresArray = DTTools.throwOutUnknowns(featuresArray);
//		labelsArray = DTTools.throwOutUnknowns(labelsArray, featuresArray);
		
		numNodes = 0;
		_root = ID3(featuresArray, labelsArray, attributes, -1.0);	
		computeTreeDepth(_root, 0);
		System.out.println("numNodes: " + numNodes);
		System.out.println("treeDepth: " + treeDepth);
		try {
			writer = new BufferedWriter(new FileWriter("output/" + FILE_NAME));
			DTTools.printTree(_root, writer);		
			writer.close();
		}
		catch(IOException e) {
			e.printStackTrace();
		}
	}

	public DTNode ID3(double[][] features, double[] labels, 
			ArrayList<Double> attributes, double attributeValueIn) {
		DTNode root = new DTNode();
		root.setAttributeValueIn(attributeValueIn);

		if (DTTools.allValuesAreSame(labels) || attributes.isEmpty()) {
			root.setMostCommonLabel(DTTools.mostCommonValueIn(labels));
			return root;
		}

//		if (isImproving(valFeatures, valLabels)) { //val set			
			
			double attribute = DTTools.bestGain(features, labels, attributes);
			
			root.setDecisionAttributeOut(attribute);
			ArrayList<Double> values = DTTools.getValues(features, attribute);
			
 			for (double value : values) {
				
				double[][] subsetFeatures = DTTools.subsetFeatures(features, attribute, value);
				double[] subsetLabels = DTTools.subsetLabels(features, labels, attribute, value);
				
				if (subsetFeatures.length == 0) {
					root.setMostCommonLabel(DTTools.mostCommonValueIn(labels));
				}
				else {
					ArrayList<Double> attributesCopy = new ArrayList<Double>(attributes);
					attributesCopy.remove(attribute);
					DTNode child = ID3(subsetFeatures, subsetLabels, attributesCopy, value);
					child.setDepthInTree(root.getTreeDepth()+1);
					root.addChild(child);
				}		
				
			}
//		}		
		return root;
	}
	
	private double lastAccuracyOnValSet;
	public boolean isImproving(Matrix features, Matrix labels) {
		double accuracy = 0.0;
		Matrix featuresCopy = new Matrix(features, 0, 0, features.rows(), features.cols());
		Matrix labelsCopy = new Matrix(labels, 0, 0, labels.rows(), labels.cols());
		try {
			accuracy = measureAccuracy(featuresCopy, labelsCopy, null);
		} catch (Exception e) { e.printStackTrace(); }
		
		if (accuracy < lastAccuracyOnValSet)
			return false;
		else {
			lastAccuracyOnValSet = accuracy;
			return true;
		}
	}
	
	public void computeTreeDepth(DTNode root, int depth) {
		numNodes++;
		if (treeDepth < depth)
			treeDepth = depth;
		if (root.hasChildren()) {
			for (DTNode child : root.getChildren()) {
				child.setDepthInTree(depth + 1);
				computeTreeDepth(child, depth + 1);
			}
		}
	}

	public void predict(int row, final double[] features, double[] labels) throws Exception {
		
		DTNode node = findLeafNode(_root, features, labels);
		labels[0] = node == null ? 0 : node.getMostCommonLabel();
	}
	
	public DTNode findLeafNode(DTNode node, double[] features, double[] labels) {
		
		if (node == null || node.getMostCommonLabel() != -1)
			return node;
		
		double decisionAttribute = node.getDecisionAttributeOut();
		if (decisionAttribute == -1)
			return node;
		
		double attributeValue = features[(int)decisionAttribute];
		DTNode child = node.childWithAttributeValueIn(attributeValue);	
		return findLeafNode(child, features, labels);
	}

	private DTNode _root;
	public static BufferedWriter writer;
	private static final String FILE_NAME = "out.csv";
	
	private int numNodes;
	private int treeDepth;
}
